//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"
#include "Jffs2Directory.h"
#define _NO_CJFFS2DIRECTORY_CLASSOBJECT_
#include "_CJffs2Directory.cpp"

#include "Jffs2File.h"
#include "Jffs2DirEnumerator.h"

#include <stdio.h>
#include <stdlib.h>
#include <jffs2_misc.h>

const char illegal[] = ";+=[]',\"*\\<>/?:|",
                       villegal[] = "\"*\\<>/?:|";

CJffs2Directory::~CJffs2Directory()
{
    IoOperator::jffs2_fo_close(&m_file);
}

ECode CJffs2Directory::Sync()
{
    return NOERROR;
}

ECode CJffs2Directory::GetType(
                /* [out] */ enum FileType * pType)
{
    if (!pType)
        return ErrorToECODE(EINVAL, "CJffs2Directory::GetType");

    *pType = (enum FileType)m_file.f_type;
    return NOERROR;
}

ECode CJffs2Directory::GetAccessTime(
                /* [out] */ Int32 * pTime)
{
    int err;
    struct stat stat1;

    if (NULL == pTime)
        return ErrorToECODE(EINVAL, "CJffs2Directory::GetAccessTime");

    err = IoOperator::jffs2_fo_fstat(&m_file, &stat1);
    if (err != ENOERR)
        return ErrorToECODE(err, "CJffs2Directory::GetAccessTime");
    *pTime = stat1.st_atime;
    return NOERROR;
}

ECode CJffs2Directory::SetAccessTime(
                /* [in] */ Int32 time)
{
    struct stat stat1;
    int err;

    stat1.st_atime = time;
    stat1.st_mtime = m_file.f_data->i_mtime;
    stat1.st_mode = m_file.f_data->i_mode;

    err = IoOperator::jffs2_fo_set_fstat(&m_file, &stat1);
    if (err != ENOERR)
        return ErrorToECODE(err, "CJffs2Directory::SetAccessTime");
    return NOERROR;
}

ECode CJffs2Directory::GetModificationTime(
                /* [out] */ Int32 * pTime)
{
    int err;
    struct stat stat1;

    if (NULL == pTime)
        return ErrorToECODE(EINVAL, "CJffs2Directory::GetModificationTime");

    err = IoOperator::jffs2_fo_fstat(&m_file, &stat1);
    if (err != ENOERR)
        return ErrorToECODE(err, "CJffs2Directory::GetModificationTime");

    *pTime = stat1.st_mtime;
    return NOERROR;
}

ECode CJffs2Directory::SetModificationTime(
                /* [in] */ Int32 time)
{
    struct stat stat1;
    int err;

    stat1.st_atime = m_file.f_data->i_atime;
    stat1.st_mtime = time;
    stat1.st_mode = m_file.f_data->i_mode;

    err = IoOperator::jffs2_fo_set_fstat(&m_file, &stat1);
    if (err != ENOERR)
        return ErrorToECODE(err, "CJffs2Directory::SetModificationTime");
    return NOERROR;
}

ECode CJffs2Directory::GetCreateTime(
                /* [out] */ Int32 * pTime)
{
    int err;
    struct stat stat1;

    if (NULL == pTime)
        return ErrorToECODE(EINVAL, "CJffs2Directory::GetCreateTime");

    err = IoOperator::jffs2_fo_fstat(&m_file, &stat1);
    if (err != ENOERR)
        return ErrorToECODE(err, "CJffs2Directory::GetCreateTime");

    *pTime = stat1.st_ctime;
    return NOERROR;
}

ECode CJffs2Directory::CheckAccessMode(
                /* [in] */ enum FileAccessMode mode)
{
    UInt32 nmode = m_file.f_flag;

    switch (mode) {
        case FileAccessMode_Read:
            if (nmode & S_IRUSR) {
                return NOERROR;
            }
            break;

        case FileAccessMode_Write:
            if (nmode & S_IWUSR) {
                return NOERROR;
            }
            break;

        case FileAccessMode_ReadWrite:
            if ((nmode & S_IRUSR) && (nmode & S_IWUSR)) {
                return NOERROR;
            }
            break;

        default:
            DPRINTF(("Unknown file mode = 0x%08x!\n", mode));
            return ErrorToECODE(EINVAL, "CJffs2Directory::CheckAccessMode");
    }
    return NOERROR;
}

ECode CJffs2Directory::GetAccessMode(
                /* [out] */ enum FileAccessMode * pMode)
{
    UInt32 mode = m_file.f_flag;

    if (NULL == pMode) {
        return ErrorToECODE(EINVAL, "CJffs2Directory::GetAccessMode");
    }

    if ((mode & S_IRUSR) && (mode & S_IWUSR)) {
        *pMode = FileAccessMode_ReadWrite;
    }
    else if ((mode & S_IWUSR) && !(mode & S_IRUSR)) {
        *pMode = FileAccessMode_Write;
    }
    else {
        *pMode = FileAccessMode_Read;
    }

    return NOERROR;
}

ECode CJffs2Directory::SetAccessMode(
                /* [in] */ enum FileAccessMode mode)
{
    struct stat stat1;
    int err;

    UInt32 fmode = 0;

    switch (mode) {
        case FileAccessMode_Read:
            fmode = S_IRUSR;
            break;

        case FileAccessMode_Write:
            fmode = S_IWUSR;
            break;

        case FileAccessMode_ReadWrite:
            fmode = S_IRUSR | S_IWUSR;
            break;

        default:
            DPRINTF(("Unknown file mode = 0x%08x!\n", mode));
            return ErrorToECODE(EINVAL, "CJffs2Directory::SetAccessMode");
    }

    if (fmode == m_file.f_flag)
        return NOERROR;

    stat1.st_atime = m_file.f_data->i_atime;
    stat1.st_mtime = m_file.f_data->i_mtime;
    stat1.st_mode = m_file.f_data->i_mode & (~S_IRWXU) | fmode;

    err = IoOperator::jffs2_fo_set_fstat(&m_file, &stat1);
    if (err != ENOERR)
        return ErrorToECODE(err, "CJffs2Directory::SetAccessMode");
    return NOERROR;
}

ECode CJffs2Directory::Lookup(
                /* [in] */ WString wstrPath,
                /* [out] */ IBasicFile * * piBasicFile)
{
    CJffs2Directory *pBasicDir = NULL;
    CJffs2File *pBasicFile = NULL;
    n_file pFile;
    char szPathName[PATH_MAX_LEN];
    int error;

    if ((NULL == wstrPath) || (L'\0' == *wstrPath) || (wcslen(wstrPath) >= PATH_MAX_LEN) ||
        (NULL == piBasicFile)) {
        return ErrorToECODE(EINVAL, "CJffs2Directory::Lookup");
    }

    WcsToMbs(szPathName, (const wchar_t *)wstrPath, PATH_MAX_LEN);

    error = CheckFileName(szPathName);
    if (error != ENOERR)
        goto Exit;

    error = IoOperator::jffs2_open(m_file.f_data, szPathName, O_RDWR, &pFile);
    if (error != ENOERR) {
        if (error == EISDIR) {
            //We want to open a directory
            error = IoOperator::jffs2_opendir(m_file.f_data, szPathName, &pFile);
            if (error != ENOERR) {
                goto Exit;
            }
        }
        else
            goto Exit;
    }

    if (pFile.f_type == ELA_FILE_TYPE_FILE) {
        pBasicFile = new CJffs2File(pFile);
        if (!pBasicFile) {
            jffs2_iput(pFile.f_data);
            error = ENOMEM;
            goto Exit;
        }
        pBasicFile->AddRef();
        *piBasicFile = pBasicFile;
    }
    else {
        assert(pFile.f_type == ELA_FILE_TYPE_DIRECTORY);
        pBasicDir = new CJffs2Directory(pFile);
        if (!pBasicDir) {
            jffs2_iput(pFile.f_data);
            error = ENOMEM;
            goto Exit;
        }
        pBasicDir->AddRef();
        *piBasicFile = pBasicDir;
    }

    return NOERROR;
Exit:
    return ErrorToECODE(error, "CJffs2Directory::Lookup");
}

ECode CJffs2Directory::CreateFile(
                /* [in] */ WString wstrPath,
                /* [in] */ enum FileAccessMode cmode,
                /* [in] */ Boolean bExclusive,
                /* [out] */ IBasicFile * * piBasicFile)
{
    CJffs2File *pBasicFile = NULL;
    n_file pFile;
    char szPathName[PATH_MAX_LEN];
    int error;
    int fmode;

    if ((NULL == wstrPath) || (L'\0' == *wstrPath) || (wcslen(wstrPath) >= PATH_MAX_LEN) ||
        (NULL == piBasicFile)) {
        return ErrorToECODE(EINVAL, "CJffs2Directory::CreateFile");
    }

    WcsToMbs(szPathName, (const wchar_t *)wstrPath, PATH_MAX_LEN);

    error = CheckFileName(szPathName);
    if (error != ENOERR)
        goto Exit;

    switch (cmode) {
        case FileAccessMode_Read:
            fmode = S_IRUSR;
            break;

        case FileAccessMode_Write:
            fmode = S_IWUSR;
            break;

        case FileAccessMode_ReadWrite:
            fmode = S_IRUSR | S_IWUSR;
            break;

        default:
            DPRINTF(("Unknown file mode = 0x%08x!\n", cmode));
            error = EINVAL;
            goto Exit;
    }
    if (bExclusive) {
        error = IoOperator::jffs2_open(m_file.f_data, szPathName, O_CREAT|O_EXCL|fmode|S_IXUSR, &pFile);
        if (error != ENOERR) {
            goto Exit;
        }
    }
    //BUGBUG:Should handle the case of opening a exist writeable file with readonly flag
    else {
        error = IoOperator::jffs2_open(m_file.f_data, szPathName, O_CREAT|fmode|S_IXUSR, &pFile);
        if (error != ENOERR) {
            goto Exit;
        }
    }

    pBasicFile = new CJffs2File(pFile);
    if (NULL == pBasicFile) {
        jffs2_iput(pFile.f_data);
        error = ENOMEM;
        goto Exit;
    }
    pBasicFile->AddRef();
    *piBasicFile = pBasicFile;
    return NOERROR;

Exit:
    return ErrorToECODE(error, "CJffs2Directory::CreateFile");
}

ECode CJffs2Directory::DeleteFile(
                /* [in] */ WString wstrPath)
{
    int nError;
    char szPathName[PATH_MAX_LEN];

    if ((NULL == wstrPath) || (L'\0' == *wstrPath) || (wcslen(wstrPath) >= PATH_MAX_LEN)) {
        nError = EINVAL;
        goto Exit;
    }

    WcsToMbs(szPathName, (const wchar_t *)wstrPath, PATH_MAX_LEN);

    nError = CheckFileName(szPathName);
    if (nError != ENOERR)
        goto Exit;

    nError = IoOperator::jffs2_ops_unlink(m_file.f_data, szPathName);
    if (nError != ENOERR)
        goto Exit;

    return NOERROR;
Exit:
    return ErrorToECODE(nError, "CJffs2Directory::DeleteFile");
}

ECode CJffs2Directory::Rename(
                /* [in] */ WString wstrOldPath,
                /* [in] */ IBasicDirectory * pNewDir,
                /* [in] */ WString wstrNewPath)
{
    int err;
    char szPathName1[PATH_MAX_LEN];
    char szPathName2[PATH_MAX_LEN];

    if ((NULL == wstrOldPath) || (L'\0' == *wstrOldPath) || (wcslen(wstrOldPath) >= PATH_MAX_LEN) ||
        (NULL == wstrNewPath) || (L'\0' == *wstrNewPath) || (wcslen(wstrNewPath) >= PATH_MAX_LEN) ||
        (NULL == pNewDir)) {
        err = EINVAL;
        goto Exit;
    }

    WcsToMbs(szPathName1, (const wchar_t *)wstrOldPath, PATH_MAX_LEN);

    WcsToMbs(szPathName2, (const wchar_t *)wstrNewPath, PATH_MAX_LEN);
    err = CheckFileName(szPathName2);
    if (err != ENOERR)
        goto Exit;

    err = IoOperator::jffs2_ops_rename(m_file.f_data, szPathName1, ((CJffs2Directory *)pNewDir)->m_file.f_data, szPathName2);
    if (err != ENOERR)
        goto Exit;

    return NOERROR;
Exit:
    return ErrorToECODE(err, "CJffs2Directory::Rename");
}

ECode CJffs2Directory::CreateDirectory(
                /* [in] */ WString wstrPath,
                /* [in] */ enum FileAccessMode cmode)
{
    int fmode;
    int err;
    char szPathName[PATH_MAX_LEN];

    if ((NULL == wstrPath) || (L'\0' == *wstrPath) || (wcslen(wstrPath) >= PATH_MAX_LEN)) {
        err = EINVAL;
        goto Exit;
    }

    WcsToMbs(szPathName, (const wchar_t *)wstrPath, PATH_MAX_LEN);

    err = CheckFileName(szPathName);
    if (err != ENOERR)
        goto Exit;

    switch (cmode) {
        case FileAccessMode_Read:
            fmode = S_IRUSR;
            break;

        case FileAccessMode_Write:
            fmode = S_IWUSR;
            break;

        case FileAccessMode_ReadWrite:
            fmode = S_IRUSR | S_IWUSR;
            break;

        default:
            DPRINTF(("Unknown file mode = 0x%08x!\n", cmode));
            err = EINVAL;
            goto Exit;
    }

    err = IoOperator::jffs2_ops_mkdir(m_file.f_data, szPathName, fmode);
    if (err != ENOERR)
        goto Exit;

    return NOERROR;
Exit:
    return ErrorToECODE(err, "CJffs2Directory::CreateDirectory");
}

ECode CJffs2Directory::DeleteDirectory(
                /* [in] */ WString wstrPath)
{
    int nError;
    char szPathName[PATH_MAX_LEN];

    if ((NULL == wstrPath) || (L'\0' == *wstrPath) || (wcslen(wstrPath) >= PATH_MAX_LEN)) {
        nError = EINVAL;
        goto Exit;
    }

    WcsToMbs(szPathName, (const wchar_t *)wstrPath, PATH_MAX_LEN);

    nError = CheckFileName(szPathName);
    if (nError != ENOERR)
        goto Exit;

    nError = IoOperator::jffs2_ops_rmdir(m_file.f_data, szPathName);
    if (nError != ENOERR)
        goto Exit;

    return NOERROR;
Exit:
    return ErrorToECODE(nError, "CJffs2Directory::DeleteDirectory");
}

ECode CJffs2Directory::GetDirEntries(
                /* [out] */ IWStringEnumerator * * piEntryNameEnumerator)
{
    IWStringEnumerator *pDirEnum;
    int err;

    if (NULL == piEntryNameEnumerator) {
        err = EINVAL;
        goto Exit;
    }

    pDirEnum = new CJffs2DirStrEnumerator(this);
    if (NULL == pDirEnum) {
        err = ENOMEM;
        goto Exit;
    }

    *piEntryNameEnumerator = pDirEnum;
    pDirEnum->AddRef();

    return NOERROR;
Exit:
    return ErrorToECODE(err, "CJffs2Directory::GetDirEntries");
}

int CJffs2Directory::CheckFileName(
                /* [in] */ char * pathname)
{
    char *p, c, *pname;
    int len = 0, ndot, allspace = 0;
    int fLongname = 0;

    p = pathname + strlen(pathname);
    while (p != pathname && *p != '\\' && *p != '/')
        --p;

    if (p != pathname)
        ++p;
    pname = p;

    if (strlen(pname) > NAME_MAX_LEN)
        return EINVAL;

    /*
     * Scan filename for illegal characters.
     */
    for (ndot = 0; (c = (*p));++p,++len) {
        /*if char illegal in a short name
         * or a long name
         */
        if (strchr(villegal, c) || ((unsigned char)c < 0x20)) { // note: the variable 'c' is a signed char, and 0x80 is '-0'
            return EINVAL;  //no ASCII byte (0x00-0x7F) can appear as part of any other character
        }               //also Chinese character, so c < 0x20 is illegal.

        /*
         * If char is illegal in a short name,
         * but OK in a long, use long format.
         */
        if ((unsigned char)c >= 0x80 || 0x20 == c || strchr(illegal, c)) {
            fLongname = 1;
        }

        if (c != 0x20) {
            allspace = 1;
        }

        /*
         * More than one dot in a filename oR The first char
         * is dot means it has to be represented as a long
         * filename.
         */
        if ('.' == c) {
            if (ndot++> 0 || p == pname) {
                fLongname = 1;
            }
        }
    }
    if (allspace == 0) {
        return EINVAL;//the file name is composed all by space character
    }

    if (len == 1 && (unsigned char)pathname[0] > 0x80)
        return EINVAL; // single byte range from 0x81 to 0xFF is illegal.

    return ENOERR;
}

#if 0
Boolean CJffs2Directory::IsValidFatFile(class CFatFile *pCFatFile)
{
    if (NULL == pCFatFile
            || NULL == pCFatFile->f_node
            || NULL == pCFatFile->pFatDir) {
        return FALSE;
    }
    return TRUE;
}

ECode CJffs2Directory::AddFileMonitor(
                /* [in] */ Boolean bManualReset,
                /* [in] */ UInt32 dwInitialState,
                /* [out] */ IEvent** pEvent)
{
    if (NULL == pEvent)
        return EINVAL;

    CChangeNotifier *pNotifier = m_pCFatFile->pFatDir->p_Sharedata->pNotifier;

    if (NULL == pNotifier) {
        pNotifier = new CChangeNotifier;
        if (NULL == pNotifier)
            return E_OUT_OF_MEMORY;
    }

    *pEvent = pNotifier->AddMonitor(m_pCFatFile->f_node, bManualReset, dwInitialState);

    if (NULL == *pEvent)
        return E_FAIL;
    /* Make sure node who has been monitored could not be Release. */
    m_pCFatFile->pFatDir->ref_node(m_pCFatFile->f_node);
    m_pCFatFile->pFatDir->p_Sharedata->pNotifier = pNotifier;

    return NOERROR;
}

ECode CJffs2Directory::RemoveFileMonitor(
                /* [in] */ IEvent * pEvent)
{
    CChangeNotifier *pNotifier = m_pCFatFile->pFatDir->p_Sharedata->pNotifier;
    assert(pNotifier);

    if (pNotifier->RemoveMonitor(m_pCFatFile->f_node, pEvent))
        return E_FAIL;

    m_pCFatFile->pFatDir->deref_node(m_pCFatFile->f_node);

    return NOERROR;
}

inline void CJffs2Directory::Lock()
{
//    EnterCriticalSection(&this->m_pCFatFile->f_node->n_lock);
}

inline void CJffs2Directory::Unlock()
{
//    LeaveCriticalSection(&this->m_pCFatFile->f_node->n_lock);
}

#endif
